import * as THREE from 'three';
import CANNON from 'cannon';

const getColors = (points: any[], colors: any[], geometry: any, axis: any, reverse: any) => {
  geometry.computeBoundingBox();
  const newColors = [];
  const bbox = geometry.boundingBox;
  const size = new THREE.Vector3().subVectors(bbox.max, bbox.min);
  const normalized = new THREE.Vector3();
  for (let index = 0; index < points.length; index++) {
    let normalizedAxis = normalized.subVectors(points[index], bbox.min).divide(size)[axis];
    if (reverse) {
      normalizedAxis = 1 - normalizedAxis;
    }

    newColors[index] = [1, 1, 0];
    for (let c = 0; c < colors.length - 1; c++ ) {
      const colorDiff = colors[c + 1].stop - colors[c].stop;
      if (normalizedAxis >= colors[c].stop && normalizedAxis <= colors[c + 1].stop) {
        var localNormalizedAxis = (normalizedAxis - colors[c].stop) / colorDiff;
        newColors[index] = (colors[c].color.clone().lerp(colors[c + 1].color, localNormalizedAxis) as THREE.Color).toArray();
      }
    }
  }

  return newColors.flat();
}

export default class CannonHelper {
  scene: THREE.Scene;
  sun: THREE.DirectionalLight | undefined;
  currentMaterial: THREE.MeshLambertMaterial | undefined;
  settings: any;
  particleGeo: any;
  particleMaterial: any;
  constructor(scene: THREE.Scene) {
    this.scene = scene;
    this.settings = {
      stepFrequency: 60,
      quatNormalizeSkip: 2,
      quatNormalizeFast: true,
      gx: 0,
      gy: 0,
      gz: 0,
      iterations: 3,
      tolerance: 0.0001,
      k: 1e6,
      d: 3,
      scene: 0,
      paused: false,
      rendermode: "solid",
      constraints: false,
      contacts: false,
      cm2contact: false,
      normals: false,
      axes: false,
      particleSize: 0.1,
      shadows: false,
      aabbs: false,
      profiling: false,
      maxSubSteps: 3
    }
    this.particleGeo = new THREE.SphereGeometry(1, 16, 8);
    this.particleMaterial = new THREE.MeshLambertMaterial({
      color: 0xff0000
    });
  }

  set shadowTarget(obj: THREE.Object3D<THREE.Object3DEventMap>) {
    if (this.sun !== undefined) this.sun.target = obj;
  }

  addVisual(body: any, name: any, castShadow = false, receiveShadow = true) {
    body.name = name;
    if (this.currentMaterial === undefined) this.currentMaterial = new THREE.MeshLambertMaterial({
      color: 0x888888
    });
    let mesh;
    if (body instanceof CANNON.Body) mesh = this.shape2Mesh(body, castShadow, receiveShadow);
    if (mesh) {
      body.threemesh = mesh;
      mesh.castShadow = castShadow;
      mesh.receiveShadow = receiveShadow;
      this.scene.add(mesh);
    }
  }

  shape2Mesh(body, castShadow, receiveShadow) {
    const obj = new THREE.Object3D();
    // const material = this.currentMaterial;
    // const game = this;
    let index = 0;
    body.shapes.forEach(function (shape) {
      let mesh;
      let geometry;
      let v0, v1, v2;
      switch (shape.type) {
        case CANNON.Shape.types.HEIGHTFIELD:
          geometry = new THREE.BufferGeometry();
          v0 = new CANNON.Vec3();
          v1 = new CANNON.Vec3();
          v2 = new CANNON.Vec3();
          const points = [];
          for (let xi = 0; xi < shape.data.length - 1; xi++) {
            for (let yi = 0; yi < shape.data[xi].length - 1; yi++) {
              for (let k = 0; k < 2; k++) {
                shape.getConvexTrianglePillar(xi, yi, k === 0);
                v0.copy(shape.pillarConvex.vertices[0]);
                v1.copy(shape.pillarConvex.vertices[1]);
                v2.copy(shape.pillarConvex.vertices[2]);
                v0.vadd(shape.pillarOffset, v0);
                v1.vadd(shape.pillarOffset, v1);
                v2.vadd(shape.pillarOffset, v2);
                points.push(
                  new THREE.Vector3(v0.x, v0.y, v0.z),
                  new THREE.Vector3(v1.x, v1.y, v1.z),
                  new THREE.Vector3(v2.x, v2.y, v2.z)
                );
              }
            }
          }

          geometry.setFromPoints(points);
          // geometry.setAttribute('position', new THREE.Float32BufferAttribute(points, 3));
          geometry.computeBoundingSphere();
          geometry.computeVertexNormals();
          var rev = true;
          const colors = [{
            stop: 0,
            color: new THREE.Color(0xf12711)
          }, {
            stop: .25,
            color: new THREE.Color(0xf5af19)
          }, {
            stop: .5,
            color: new THREE.Color(0xfffc00)
          }, {
            stop: .75,
            color: new THREE.Color(0x00e969)
          }, {
            stop: 1,
            color: new THREE.Color(0x053105)
          }];
          
          const newColors = getColors(points, colors, geometry, 'z', rev);
          geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(newColors), 3));
          mesh = new THREE.Mesh(geometry,  new THREE.MeshLambertMaterial({
            vertexColors: true,
            wireframe: false
          }));
          break;
        default:
          throw new Error('Visual type not recognized: ' + shape.type);
      }
      mesh.receiveShadow = receiveShadow;
      mesh.castShadow = castShadow;
      mesh.traverse((child: any) => {
        if (child.isMesh) {
          child.castShadow = castShadow;
          child.receiveShadow = receiveShadow;
        }
      });
      var o = body.shapeOffsets[index];
      var q = body.shapeOrientations[index++];
      mesh.position.set(o.x, o.y, o.z);
      mesh.quaternion.set(q.x, q.y, q.z, q.w);
      obj.add(mesh);
    });
    return obj;
  }
  updateBodies(world: any) {
    world.bodies.forEach(function (body: any) {
      if (body.threemesh !== undefined) {
        body.threemesh.position.copy(body.position);
        body.threemesh.quaternion.copy(body.quaternion);
      }
    });
  }
}